package cn.maihe.elg.operation.centers.epoint.sanming.auth;

import cn.maihe.elg.operation.centers.epoint.sanming.config.EpointAuthConfig;
import cn.maihe.elg.operation.common.auth.Authorization;
import cn.maihe.elg.operation.config.ElgAuthConfig;
import cn.maihe.elg.operation.utils.ElgLogger;
import cn.maihe.elg.operation.utils.SM3Util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.pqc.legacy.math.linearalgebra.ByteUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * @Description 新点签名验签算法
 * 报文签名使用国密消息摘要算法（SM3），将报文字段按照ASCII升序排序，并按照“参数=参数值”的模式，用“&”字符拼接成字符串，最后使用appsecret对报文进行签名。
 * @Author WJH
 * @Date 2021/07/12
 */
@Slf4j
@Component
public class EpointAuthorization implements Authorization {
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final ElgAuthConfig elgAuthConfig;
    private final EpointAuthConfig epointAuthConfig;

    public EpointAuthorization(ElgAuthConfig elgAuthConfig, EpointAuthConfig epointAuthConfig) {
        this.elgAuthConfig = elgAuthConfig;
        this.epointAuthConfig = epointAuthConfig;

        Assert.hasText(elgAuthConfig.getPubKey(), () -> "本系统公钥不能为空");
        Assert.hasText(elgAuthConfig.getPriKey(), () -> "本系统私钥不能为空");

        Assert.hasText(epointAuthConfig.getAppKey(), () -> "漳州新点提供的appKey不能为空");
        Assert.hasText(epointAuthConfig.getAppSecret(), () -> "漳州新点提供的appSecret不能为空");

    }

    @Override
    public String signature(Object body, String timestamp) {
        ElgLogger.debug(log, l -> l.debug("待签名请求对象：{}", toJsonString(body)));

        String summaryMsg = this.buildMessageDigest(body, "sign");
        ElgLogger.debug(log, l -> l.debug("待签名摘要信息：{}", summaryMsg));

        String unSigned = String.format("%s&appsecret=%s", summaryMsg, epointAuthConfig.getAppSecret());
        ElgLogger.debug(log, l -> l.debug("待签名字符串：{}", unSigned));

        String signed =  ByteUtils.toHexString(SM3Util.hash(unSigned.getBytes(StandardCharsets.UTF_8)));
        ElgLogger.debug(log, l -> l.debug("签名后字符串：{}", signed));

        return signed;
    }

    @Override
    public boolean verifySignature(Object reqBody, String signedMsg, String timestamp) {
        ElgLogger.debug(log, l -> l.debug("待验签请求对象：{}", toJsonString(reqBody)));

        String summaryMsg = this.buildMessageDigest(reqBody, "sign");
        ElgLogger.debug(log, l -> l.debug("待验签消息摘要：{}", summaryMsg));

        String unSigned = String.format("%s&appsecret=%s", summaryMsg, epointAuthConfig.getAppSecret());
        ElgLogger.debug(log, l -> l.debug("待验签字符串：{}", unSigned));

        String signed = ByteUtils.toHexString(SM3Util.hash(unSigned.getBytes(StandardCharsets.UTF_8)));
        ElgLogger.debug(log, l -> l.debug("验签后字符串：{}; 源签名字符串：{}", signed, signedMsg));

        boolean verifyTF = signed.equalsIgnoreCase(signedMsg);
        ElgLogger.debug(log, l -> l.debug("验签结果：[{}]", verifyTF));

        return verifyTF;
    }

    String buildMessageDigest(Object sourceBody, String... excludeKey) {
        List<String> excludeKeyList = (excludeKey == null || excludeKey.length == 0) ? null : Arrays.asList(excludeKey);

        TreeMap<String, Object> treeMap = objectMapper.convertValue(sourceBody, TreeMap.class);
        String summaryMsg = treeMap.entrySet().stream()
                .filter(entry -> excludeKeyList == null || !excludeKeyList.contains(entry.getKey()))
                .filter(entry -> StringUtils.isNotBlank(String.valueOf(entry.getValue())))
                .map(entry -> entry.getKey() +"="+ entry.getValue()).collect(Collectors.joining("&"));
        return summaryMsg;
    }

    private String toJsonString(Object body) {
        try {
            return objectMapper.writeValueAsString(body);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }


}
